import wx

from threading import Timer
from time import time
from math import ceil, floor
from LMG.Utility.constants import *

class ScheduledCommand(object):
    """
    Command
    """
    def __init__(self, active, frequency, weekschedule, time_start, time_end, command, value):
        # Command info
        self.active = active
        self.frequency = frequency
        self.weekschedule = weekschedule
        self.time_start = time_start
        self.time_end = time_end
        self.command = command
        self.value = value
        
        # Flags
        self.changed = False
        self.nextruntime = 0
        self.lastruntime = 0

    def computeNextRunTime(self, reftime):
        if self.frequency == 'weeklymulti':
            mindate_dt = wx.DateTimeFromTimeT(max(reftime, self.time_start))
            reftime_dt = wx.DateTimeFromTimeT(reftime)
            schedtime_dt = wx.DateTimeFromTimeT(self.time_start)
            reftime_t = wx.DateTimeFromHMS(reftime_dt.GetHour(), reftime_dt.GetMinute(), reftime_dt.GetSecond())
            schedtime_t = wx.DateTimeFromHMS(schedtime_dt.GetHour(), schedtime_dt.GetMinute(), schedtime_dt.GetSecond())
            minday = mindate_dt.GetWeekDay()
            nbdays = 0
            scheddayfound = False
            for i in xrange(minday, minday + 8):
                if self.weekschedule[i%7] == '1' and \
                   (not mindate_dt.IsSameDate(reftime_dt) or \
                    (i != reftime_dt.GetWeekDay() or not reftime_t.IsLaterThan(schedtime_t))):
                    scheddayfound = True
                    break
                nbdays += 1
            if scheddayfound:
                schedtime_dt.SetYear(mindate_dt.GetYear())
                schedtime_dt.SetMonth(mindate_dt.GetMonth())
                schedtime_dt.SetDay(mindate_dt.GetDay())
                schedtime_dt += wx.TimeSpan(nbdays * 24)
                self.nextruntime = schedtime_dt.GetTicks()
                print self.nextruntime - reftime
            else:
                self.nextruntime = 0

        else:
            self.nextruntime = self.time_start
            # Next day
            if self.frequency == 'daily':
                if reftime > self.time_start:
                    self.nextruntime += ceil((reftime - self.time_start) / 86400.) * 86400
            # Next week
            elif self.frequency == 'weekly':
                if reftime > self.time_start:
                    self.nextruntime += ceil((reftime - self.time_start) / 604800.) * 604800

        # Expiration date
        if self.frequency != 'once' and self.time_end:
            if self.nextruntime >=  self.time_end + 86400:
                self.nextruntime = 0
    
    def computeLastRunTime(self, reftime):
        if self.time_start >= reftime or not self.active or self.command in ('none', 'exit') \
           or (self.time_end and self.time_start >= self.time_end + 86400):
            self.lastruntime = 0
        else:
            if self.time_end:
                lastpastvaliddate = min(self.time_end + 86400, reftime)
            else:
                lastpastvaliddate = reftime
            if self.frequency == 'weeklymulti':
                maxdate_dt = wx.DateTimeFromTimeT(lastpastvaliddate)
                reftime_dt = wx.DateTimeFromTimeT(reftime)
                schedtime_dt = wx.DateTimeFromTimeT(self.time_start)
                reftime_t = wx.DateTimeFromHMS(reftime_dt.GetHour(), reftime_dt.GetMinute(), reftime_dt.GetSecond())
                schedtime_t = wx.DateTimeFromHMS(schedtime_dt.GetHour(), schedtime_dt.GetMinute(), schedtime_dt.GetSecond())
                maxday = maxdate_dt.GetWeekDay()
                nbdays = 0
                lastscheddayfound = False
                for i in xrange(maxday, maxday - 8, -1):
                    if self.weekschedule[(i - 1)%7] == '1' and (not maxdate_dt.IsSameDate(reftime_dt) or
                                                          (i != reftime_dt.GetWeekDay() or reftime_t.IsLaterThan(schedtime_t))):
                        lastscheddayfound = True
                        break
                    nbdays += 1
                if lastscheddayfound:
                    schedtime_dt.SetYear(maxdate_dt.GetYear())
                    schedtime_dt.SetMonth(maxdate_dt.GetMonth())
                    schedtime_dt.SetDay(maxdate_dt.GetDay())
                    schedtime_dt -= wx.TimeSpan(nbdays * 24)
                    self.lastruntime = schedtime_dt.GetTicks()
                    if self.lastruntime < self.time_start:
                        self.lastruntime = 0
                else:
                    self.lastruntime = 0
            else:
                self.lastruntime = self.time_start
                if self.frequency == 'daily':
                    self.lastruntime += floor((lastpastvaliddate - self.time_start) / 86400.) * 86400
                elif self.frequency == 'weekly':
                    self.lastruntime += floor((lastpastvaliddate - self.time_start) / 604800.) * 604800


class CommandScheduler(object):
    """
    Command Scheduler
    """
    def __init__(self):
        self.commands = {'none': (-1, None),
                         'stopall': (ACTION_STOPALL, None),
                         'unstopall': (ACTION_UNSTOPALL, None),
                         'queueactive': (ACTION_QUEUEACTIVE, None),
                         'clearall': (ACTION_CLEARALL, None),
                         'maxuploadvolume': (-1, int),
                         'maxdownloadvolume': (-1, int),
                         'maxuploadrate': (-1, int),
                         'maxseeduploadrate': (-1, int),
                         'maxdownloadrate': (-1, int),
                         'numsimdownload': (-1, int),
                         'rsstimer': (-1, bool),
                         'webservice': (-1, bool),
                         'scandiractive': (-1, bool),
                         'exit': (ACTION_EXIT, None)
                         }
        self.commandsList = ['none', 'stopall', 'unstopall', 'queueactive', 'clearall', 'maxuploadrate',
                             'maxseeduploadrate', 'maxdownloadrate', 'maxuploadvolume', 'maxdownloadvolume',
                             'numsimdownload', 'rsstimer', 'webservice', 'scandiractive', 'exit']
#        self.commandsList = self.commands.keys()
#        self.commandsList.sort()

        self.schedcommands = []
        self.nextschedcommand = None
        self.running = False
        self.stopping = False

        self.load()

        if utility.config.Read('schedautostart', "boolean"):
            self.start()
        
    def getCommandName(self, command):
        action = self.commands[command][0]
        if action >= 0:
            return utility.actions[action].shortdesc
        elif command == 'none':
            return _('None')
        elif command == 'maxuploadrate':
            return _('Maximum upload rate when downloading')
        elif command == 'maxseeduploadrate':
            return _('Maximum upload rate when not downloading')
        elif command == 'maxdownloadrate':
            return _('Maximum download rate')
        elif command == 'maxuploadvolume':
            return _('Maximum upload volume (MB)')
        elif command == 'maxdownloadvolume':
            return _('Maximum download volume (MB)')
        elif command == 'numsimdownload':
            return _('Maximum number of active torrents')
        elif command == 'rsstimer':
            return _('RSS Timer')
        elif command == 'webservice':
            return _('Web Service')
        elif command == 'scandiractive':
            return _('Directory Scanner')
        else:
            raise Exception, 'unknown command: ' + command
        
    def getCommandValueType(self, command):
        return self.commands[command][1]

    def newCommand(self):
        now = int(time())
        command = ScheduledCommand(True, "once", "0000000", now, 0, 'none', '---')
        
        index = 0
        while index < len(self.schedcommands) and self.schedcommands[index].time_start <= now:
            index += 1
        self.schedcommands.insert(index, command)

        return command, index

    def load(self):
        """
        Load commands from file
        """
        commands = utility.config.Read('schedcommands', 'bencode-list')
        for command in commands:
            com = ScheduledCommand(*command)
            if com.command in self.commands:
                self.schedcommands.append(com)
        
    def save(self):
        """
        Save commands to file
        """
        commands = []
        for com in self.schedcommands:
            command = (com.active, com.frequency, com.weekschedule, com.time_start, com.time_end, com.command, com.value)
            commands.append(command)
        utility.config.Write('schedcommands', commands, 'bencode-list')
            
    def start(self):
        """
        Start the command scheduler
        """
        if not self.running:
            now = time()
            self.stopping = False
            self.running = True
            # Replay last run if needed
            if utility.config.Read('schedrunpast', "boolean"):
                self.playLastRuns(now)
            # Init all run times
            for com in self.schedcommands:
                com.computeNextRunTime(now)
            self.scheduleNext(now)
    
    def stop(self):
        """
        Stop the command scheduler
        """
        if self.running:
            self.stopping = True
            try:
                self.schedulertimer.cancel()
            except:
                pass
            self.running = False
            self.nextschedcommand = None

    def postCommand(self, schedcommand):
        """
        Run a command in main thread
        """
        utility.queue.invokeLater(self.runScheduledCommand, [schedcommand])

    def runScheduledCommand(self, schedcommand):
        """
        Run a command
        """
        # Stopping?...
        if self.stopping:
            return
        
        reftime = schedcommand.nextruntime
        schedcommand.nextruntime = 0
        self.scheduleNext(reftime)

        # No action?
        if not schedcommand.command in self.commands \
           or schedcommand.command == 'none':
            return

        if schedcommand.command == 'exit':
            utility.frame.OnCloseWindow(silent = True)
            
        elif schedcommand.command in ('maxuploadrate', 'maxseeduploadrate'):
            if int(schedcommand.value) < 3:
                schedcommand.value = "0"
            utility.config.Write(schedcommand.command, schedcommand.value)
            utility.config.Flush()

        elif schedcommand.command in ('maxuploadvolume', 'maxdownloadvolume'):
            utility.config.Write(schedcommand.command, int(schedcommand.value), "int")
            # Reset
            if int(schedcommand.value) == 0:
                if 'up' in schedcommand.command:
                    utility.queue.volume['up'] = 0
                elif 'down' in schedcommand.command:
                    utility.queue.volume['down'] = 0
            
        elif schedcommand.command == 'numsimdownload':
            utility.config.Write('numsimdownload', schedcommand.value)
            utility.config.Flush()
            utility.queue.UpdateRunningTorrentCounters()
            utility.queue.changeABCParams()

        elif schedcommand.command == 'rsstimer':
            utility.window['rss'].setTimerActivity(schedcommand.value=="1")

        elif schedcommand.command == 'webservice':
            utility.actions[ACTION_WEBSERVICE].action(activate = schedcommand.value == "1")

        elif schedcommand.command == 'scandiractive':
            utility.config.Write(schedcommand.command, schedcommand.value=="1", "boolean")
            utility.config.Flush()

        elif self.commands[schedcommand.command][0] > 0:
            utility.actions[self.commands[schedcommand.command][0]].action()
            
        else:
            utility.config.Write(schedcommand.command, schedcommand.value)
            utility.config.Flush()
                
    def scheduleNext(self, reftime):
        """
        Find next command to be run
        """
        activatedschedcommand = self.nextschedcommand
        # Find next to be run
        self.nextschedcommand = None
        for schedcommand in self.schedcommands:
            if schedcommand.active and schedcommand.command != 'none' and schedcommand.nextruntime \
               and schedcommand.nextruntime >= reftime and (self.nextschedcommand == None or schedcommand.nextruntime < self.nextschedcommand.nextruntime):
                self.nextschedcommand = schedcommand
            else:
                schedcommand.changed = False

        if self.nextschedcommand is not None and (self.nextschedcommand != activatedschedcommand or self.nextschedcommand.changed):
            if activatedschedcommand is not None or self.nextschedcommand.changed:
                # If different from activated one or changed, disable then activate the new one
                try:
                    self.schedulertimer.cancel()
                    self.nextschedcommand.changed = False
                except:
                    pass
            # Activate command
            if not self.stopping:
#                print self.nextschedcommand.nextruntime - reftime, self.nextschedcommand
                self.schedulertimer = Timer(self.nextschedcommand.nextruntime - reftime, self.postCommand, [self.nextschedcommand])
                self.schedulertimer.start()
                
    def playLastRuns(self, reftime):
        """
        Run old commands
        """
        for schedcom in self.schedcommands:
            schedcom.computeLastRunTime(reftime)
        lastcommands = [(c.lastruntime, c) for c in self.schedcommands if c.lastruntime]
        # Sort by lastruntime
        commands = []
        lastcommands.sort(reverse = True)
        for t in lastcommands:
            if t[1].command not in commands:
                commands.append(t[1].command)
                self.postCommand(t[1])
        #lastcommands.sort()
        #for t in lastcommands:
        #    self.postCommand(t[1])
